{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d60da64f",
   "metadata": {},
   "source": [
    "可以在[Bookshop.org](https://bookshop.org/a/98697/9781098155438) 和\n",
    "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)获取纸制版和电子版的*Think Python 3e*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ae5a86f8",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from os.path import basename, exists\n",
    "\n",
    "def download(url):\n",
    "    filename = basename(url)\n",
    "    if not exists(filename):\n",
    "        from urllib.request import urlretrieve\n",
    "\n",
    "        local, _ = urlretrieve(url, filename)\n",
    "        print(\"Downloaded \" + str(local))\n",
    "    return filename\n",
    "\n",
    "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/thinkpython.py');\n",
    "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/diagram.py');\n",
    "\n",
    "import thinkpython"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e826e661",
   "metadata": {},
   "source": [
    "# 第14章:类和函数\n",
    "\n",
    "现在你知道如何使用函数组织代码，意识如何使用内建的类型来组织数据。下一步是**面对对象编程 object-oriented programming**，使用用户定义的类型同时组织代码和数据。\n",
    "\n",
    "面对对象变成是一个大话题，所以我们会慢慢推进。本章我们将从不常用的写法开始，有经验的程序员不会这样写，但它作为开端是合适的。\n",
    "\n",
    "在下一章，我们将使用额外的功能，编写更地道的代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b414d4a",
   "metadata": {},
   "source": [
    "## 用户定义的类型\n",
    "\n",
    "我们已经使用了许多Python内建类型，现在让我们定义新的类型。作为第1个例子，我们将创建一个`Time`类型，表示一天中的某个时间。用户定义的类型也叫**类class**。类的定义类似："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c9c99d2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Time:\n",
    "    \"\"\"表示一天中的某个时间。\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2414cd2",
   "metadata": {},
   "source": [
    "类定义的头部表明新类叫做`Time`。类定义体是一个文档字符串，解释这个类的作用。定义类将创建一个**类对象 class object**。\n",
    "\n",
    "类对象就像是一个创建对象的工厂。要创建`Time`对象，像函数一样调用`Time`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d318001a",
   "metadata": {},
   "outputs": [],
   "source": [
    "lunch = Time()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f63247d4",
   "metadata": {},
   "source": [
    "创建的是一个新对象，类型是`__main__.Time`，其中`__main__`是`Time`被定义的模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f37d67fd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.Time"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(lunch)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14d0c96a",
   "metadata": {},
   "source": [
    "当你打印对象时，Python会告诉你它是什么类型，存储在内存中的什么位置（`0x`前缀表明后面的数字是十六进制形式）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e2bd114a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Time object at 0x7f6c7346ab90>\n"
     ]
    }
   ],
   "source": [
    "print(lunch)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6445414",
   "metadata": {},
   "source": [
    "创建新对象的过程叫做**实例化instantiation**，这个对象是类的一个**实例instance**。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c3768ec",
   "metadata": {},
   "source": [
    "## 属性\n",
    "\n",
    "对象可以包含若干变量，这些变量叫做**属性attributes**，我们可以用句点运算符创建属性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e166701a",
   "metadata": {},
   "outputs": [],
   "source": [
    "lunch.hour = 11\n",
    "lunch.minute = 59\n",
    "lunch.second = 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3fd8858",
   "metadata": {},
   "source": [
    "这个示例创建属性`hour`, `minute`, 和 `second`，分别对应中午时刻`11:59:01`中的小时数、分钟数、秒数。\n",
    "\n",
    "以下图像显示了`lunch`赋值后的状态以及它的属性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3eb47826",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from diagram import make_frame, make_binding\n",
    "\n",
    "d1 = dict(hour=11, minute=59, second=1)\n",
    "frame = make_frame(d1, name='Time', dy=-0.3, offsetx=0.48)\n",
    "binding = make_binding('lunch', frame)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6702a353",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 177x124 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from diagram import diagram, adjust\n",
    "\n",
    "width, height, x, y = [1.77, 1.24, 0.25, 0.86]\n",
    "ax = diagram(width, height)\n",
    "bbox = binding.draw(ax, x, y)\n",
    "#adjust(x, y, bbox)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9df5b48",
   "metadata": {},
   "source": [
    "`lunch`变量名指向一个`Time`对象，包含3个属性，每个属性指向一个整数。显示一个对象和它的属性的状态图叫做**对象图 object diagram**。\n",
    "\n",
    "你可以使用点运算符读取属性的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4c4eff2b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "11"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lunch.hour"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ccfaea0",
   "metadata": {},
   "source": [
    "你可以在表达式中使用属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7ac6db21",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "719"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "total_minutes = lunch.hour * 60 + lunch.minute\n",
    "total_minutes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5e6725b",
   "metadata": {},
   "source": [
    "你可以在f字符串中的表达式中使用句点运算符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1ecdc091",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'11:59:1'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f'{lunch.hour}:{lunch.minute}:{lunch.second}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e70671d2",
   "metadata": {},
   "source": [
    "注意这个例子并不标准，我们需要将`minute`和`second`属性用0填充到2位数字。我们可以在花括号中使用**格式标识符 format specifier**。在下面的例子里，`minute`和`second`用0填充到至少2位数字。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a8a45573",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'11:59:01'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f'{lunch.hour}:{lunch.minute:02d}:{lunch.second:02d}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcbea13a",
   "metadata": {},
   "source": [
    "我们将使用这个f字符串显示`Time`对象的值。你可以像内建对象一样，将自定义对象传入函数。例如以下函数接受`Time`类的一个实例化对象。\n",
    "\n",
    "译注：由于类对象及其实例化对象的联系紧密，**`Time`类的一个实例化对象**通常简称为`Time`对象，而`Time`类对象简称`Time`类。在不导致歧义的情况下，后续均按这种方式简称。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "fc77feb2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_time(time):\n",
    "    s = f'{time.hour:02d}:{time.minute:02d}:{time.second:02d}'\n",
    "    print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b8ccbed",
   "metadata": {},
   "source": [
    "我们可以将`lunch`传入该函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "59b7f4f4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:59:01\n"
     ]
    }
   ],
   "source": [
    "print_time(lunch)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18826e53",
   "metadata": {},
   "source": [
    "## 将对象作为返回值\n",
    "\n",
    "函数可以返回对象。例如，`make_time`接受参数`hour`, `minute`与`second`，存储为`Time`对象的属性，返回新的对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "fde15b59",
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_time(hour, minute, second):\n",
    "    time = Time()\n",
    "    time.hour = hour\n",
    "    time.minute = minute\n",
    "    time.second = second\n",
    "    return time"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8a6acca",
   "metadata": {},
   "source": [
    "这些参数与对象的属性名相同可能令人惊讶，但这种写法很常见。以下是使用`make_time`创建`Time`对象的例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "f4199d7f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:59:01\n"
     ]
    }
   ],
   "source": [
    "time = make_time(11, 59, 1)\n",
    "print_time(time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05720bcb",
   "metadata": {},
   "source": [
    "## 对象是可变的\n",
    "\n",
    "假设你想要放映电影，从`9:20 PM`开始，持续`92`分钟，也即1小时32分钟。该电影在何时结束？\n",
    "\n",
    "首先让我们创建表示起始时刻的`Time`对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "57847af3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "09:20:00\n"
     ]
    }
   ],
   "source": [
    "start = make_time(9, 20, 0)\n",
    "print_time(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "001bcda9",
   "metadata": {},
   "source": [
    "要获得结束时刻，我们可以修改`Time`对象的属性，加上电影的持续时间。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "f3637b10",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10:52:00\n"
     ]
    }
   ],
   "source": [
    "start.hour += 1\n",
    "start.minute += 32\n",
    "print_time(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7007ab61",
   "metadata": {},
   "source": [
    "电影将在10:52 PM结束。让我们封装该计算到函数中，泛化该函数以接受`hours`, `minutes`, 和 `seconds`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "3468f4d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def increment_time(time, hours, minutes, seconds):\n",
    "    time.hour += hours\n",
    "    time.minute += minutes\n",
    "    time.second += seconds"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a51913e2",
   "metadata": {},
   "source": [
    "以下是使用该函数的例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "ad8177ad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10:52:00\n"
     ]
    }
   ],
   "source": [
    "start = make_time(9, 20, 0)\n",
    "increment_time(start, 1, 32, 0)\n",
    "print_time(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42d7de02",
   "metadata": {},
   "source": [
    "以下状态图显示了对象在修改前的状态。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "6f90c060",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from diagram import Frame, Binding, Value, Stack\n",
    "\n",
    "d1 = dict(hour=9, minute=20, second=0)\n",
    "obj1 = make_frame(d1, name='Time', dy=-0.25, offsetx=0.78)\n",
    "\n",
    "binding1 = make_binding('start', frame, draw_value=False, dx=0.7)\n",
    "frame1 = Frame([binding1], name='__main__', loc='left', offsetx=-0.2)\n",
    "\n",
    "binding2 = Binding(Value('time'), draw_value=False, dx=0.7, dy=0.35)\n",
    "binding3 = make_binding('hours', 1)\n",
    "binding4 = make_binding('minutes',32)\n",
    "binding5 = make_binding('seconds', 0)\n",
    "frame2 = Frame([binding2, binding3, binding4, binding5], name='increment_time', \n",
    "               loc='left', dy=-0.25, offsetx=0.08)\n",
    "\n",
    "stack = Stack([frame1, frame2], dx=-0.3, dy=-0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "93a1db71",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 340x189 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from diagram import Bbox\n",
    "\n",
    "width, height, x, y = [3.4, 1.89, 1.75, 1.5]\n",
    "ax = diagram(width, height)\n",
    "bbox1 = stack.draw(ax, x, y)\n",
    "bbox2 = obj1.draw(ax, x+0.23, y)\n",
    "bbox = Bbox.union([bbox1, bbox2])\n",
    "# adjust(x, y, bbox)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1e27667",
   "metadata": {},
   "source": [
    "在该函数中，`time`是`start`的别名，当修改`time`时，`start`也会改变。\n",
    "\n",
    "这个函数能够工作，但执行之后只有留存表示结束时刻的`start`变量，不再有一个表示开始时刻的对象。\n",
    "\n",
    "让`start`保持不变，创建一个新对象表示结束时刻可能会更好。我们可以复制`start`并修改该副本。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0128f850",
   "metadata": {},
   "source": [
    "## 复制\n",
    "\n",
    "`copy`模块提供函数`copy`，能够复制任何对象。我们可以像这样导入："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "9f74834d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from copy import copy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "940adbeb",
   "metadata": {},
   "source": [
    "要检查该函数如何工作的，让我们从一个新的`Time`对象表示起始时刻。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "770c077a",
   "metadata": {},
   "outputs": [],
   "source": [
    "start = make_time(9, 20, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "514f05b9",
   "metadata": {},
   "source": [
    "然后创建一个副本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "edced6e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "end = copy(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87d8956b",
   "metadata": {},
   "source": [
    "目前`start`和`end`包含相同的数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "509c3640",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "09:20:00\n",
      "09:20:00\n"
     ]
    }
   ],
   "source": [
    "print_time(start)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e75c1e09",
   "metadata": {},
   "source": [
    "但是`is`运算符能够确认，它们不是相同的对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "60d812f7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "start is end"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22b68a3f",
   "metadata": {},
   "source": [
    "让我们看看`==`运算符的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4d504362",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "start == end"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78ebf931",
   "metadata": {},
   "source": [
    "你可能期望`==`产生`True`，因为这两个对象包含相同的数据。但对于自定义的类，`==`默认的行为与`is`一致，只检查同一性，而非相等性。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3934fdd-d4cd-41e0-86e6-5bb78d0886a7",
   "metadata": {},
   "source": [
    "## 纯函数\n",
    "\n",
    "我们可以使用`copy`编写纯函数，不修改输入的属性。\n",
    "例如，以下函数接受一个`Time`对象以及持续时间，它将创建原始对象的副本，使用`increment_time`修改并返回副本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "85090d3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_time(time, hours, minutes, seconds):\n",
    "    total = copy(time)\n",
    "    increment_time(total, hours, minutes, seconds)\n",
    "    return total"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c181af12",
   "metadata": {},
   "source": [
    "现在让我们使用该函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "1d9cf4da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10:52:00\n"
     ]
    }
   ],
   "source": [
    "end = add_time(start, 1, 32, 0)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54b1ca4a",
   "metadata": {},
   "source": [
    "返回值是一个新的对象，表示电影的结束时间，我们可以检查`start`，确认它是不变的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "9fe30d71",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "09:20:00\n"
     ]
    }
   ],
   "source": [
    "print_time(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1233b2db",
   "metadata": {},
   "source": [
    "`add_time`是一个纯函数，不修改传入参数，唯一的效果是返回一个值。\n",
    "\n",
    "任何非纯函数能够完成的操作都可以用纯函数完成。事实上，有些编程语言只允许纯函数。使用纯函数不容易出错，但非纯函数有时更方便更高效。\n",
    "\n",
    "总体而言，我建议你优先编写纯函数，除非非纯函数有明显的优势。这种方法可以称为**函数式编程风格 functional programming style**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d9fabbc",
   "metadata": {},
   "source": [
    "## 原型与补丁\n",
    "\n",
    "在先前的例子里，`increment_time` 和 `add_time` 看似正确，但当我们尝试其他例子时，我们会看到一些错误。\n",
    "\n",
    "假设电影的开始时间是`9:40`，以下是结束时间的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "57a96bf9-7d7b-4715-a4b3-2dfad1beb670",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10:72:00\n"
     ]
    }
   ],
   "source": [
    "start = make_time(9, 40, 0)\n",
    "end = add_time(start, 1, 32, 0)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c712ebf7-7e52-490e-91d7-5f1c83334de0",
   "metadata": {},
   "source": [
    "结果并不是一个有效的时间。问题在于`increment_time`没有处理分钟或秒钟超过`60`的情况。\n",
    "\n",
    "以下是一个改进版本，如果`second`>=`60`则`minute`进1,然后检查`minute`，若`minute`>=`60`则`hour`进1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "5bada1df-be0a-4f6a-8fe3-d92bd937dc70",
   "metadata": {},
   "outputs": [],
   "source": [
    "def increment_time(time, hours, minutes, seconds):\n",
    "    time.hour += hours\n",
    "    time.minute += minutes\n",
    "    time.second += seconds\n",
    "\n",
    "    if time.second >= 60:\n",
    "        time.second -= 60\n",
    "        time.minute += 1\n",
    "\n",
    "    if time.minute >= 60:\n",
    "        time.minute -= 60\n",
    "        time.hour += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c133c5d8",
   "metadata": {},
   "source": [
    "修正`increment_time`也修正了使用该函数的`add_time`。这样以上例子就能正确工作了。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "a139b64b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:12:00\n"
     ]
    }
   ],
   "source": [
    "end = add_time(start, 1, 32, 0)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2f644a6-ca43-494e-af14-6e845b3d7973",
   "metadata": {},
   "source": [
    "但是这个函数依然不完全正确，因为传入的时间增量也可能超过`60`.我们可以这样调用函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "8c9384cb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10:72:00\n"
     ]
    }
   ],
   "source": [
    "end = add_time(start, 0, 92, 0)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72e0a08b",
   "metadata": {},
   "source": [
    "结果依然是非法的时间，因此让我们换一种方法，使用`divmod`函数。我们将创建`start`的副本，增加`minute`的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "47b04507",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "132"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "end = copy(start)\n",
    "end.minute = start.minute + 92\n",
    "end.minute"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c56355bc",
   "metadata": {},
   "source": [
    "现在`minute`为`132`，也即`2`小时`12`分钟.我们可以使用`divmod`获得相对除数`60`的商和余数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "8ce8f8bc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 12)"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "carry, end.minute = divmod(end.minute, 60)\n",
    "carry, end.minute"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43204703",
   "metadata": {},
   "source": [
    "现在`minute`正确了，我们可以将小时数添加到`hour`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "90445645",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:12:00\n"
     ]
    }
   ],
   "source": [
    "end.hour += carry\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a68ae1cd",
   "metadata": {},
   "source": [
    "现在结果是一个有效的时间。我们可以对`hour`和`second`进行类似的操作，封装整个过程到函数中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "0a9653a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def increment_time(time, hours, minutes, seconds):\n",
    "    time.hour += hours\n",
    "    time.minute += minutes\n",
    "    time.second += seconds\n",
    "    \n",
    "    carry, time.second = divmod(time.second, 60)\n",
    "    carry, time.minute = divmod(time.minute + carry, 60)\n",
    "    carry, time.hour = divmod(time.hour + carry, 24)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7437113a",
   "metadata": {},
   "source": [
    "当参数超过60时，该函数依然能够正确处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "694cfdd1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:12:00\n"
     ]
    }
   ],
   "source": [
    "end = add_time(start, 0, 90, 120)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c6329b2",
   "metadata": {},
   "source": [
    "本章展示了一种程序开发方案：**原型与补丁 prototype and patch**。我们从简单的原型开始，用更多例子测试。当我们发现错误时就修正代码。\n",
    "\n",
    "这个方法很高效，尤其是你对问题没有深入理解的时候。但是增量修复可能会生成过于复杂的代码，处理很多特例，你也不确定是否找到了所有错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39031461-49a9-4eba-a075-ef49a6f5552b",
   "metadata": {},
   "source": [
    "## 设计优先开发方案\n",
    "\n",
    "可选方案是**设计优先开发方案 design-first development**，在实现原型前做更多规划。在设计优先过程中，更高层次的见解可能会让程序更简单。\n",
    "\n",
    "我们可以将`Time`对象视为一个三位数，小时数为24进制数，分钟和秒钟数为60进制数。\n",
    "\n",
    "1分钟为60秒，1小时为3600秒。当我们编写`increment_time`时，我们以60进制进行相加,就有必要从后一位数向前进位。\n",
    "\n",
    "这一现象引伸出另一种方案：将`Time`目标转换为整数并利用Python处理整数的能力。\n",
    "\n",
    "以下是将`Time`转换为整数的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "a4427f06-10f3-478f-af4b-888297ee59ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "def time_to_int(time):\n",
    "    minutes = time.hour * 60 + time.minute\n",
    "    seconds = minutes * 60 + time.second\n",
    "    return seconds"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7e7789e",
   "metadata": {},
   "source": [
    "输出的结果是时间换算而成的总秒数。例如`01:01:01`是`1`小时`1`分钟`1`秒，是`3600`秒、`60`秒与`1`秒的总和。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "e71f9661",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "01:01:01\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "3661"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "time = make_time(1, 1, 1)\n",
    "print_time(time)\n",
    "time_to_int(time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ea525c8-4547-4bde-91c3-17f45add1bf8",
   "metadata": {},
   "source": [
    "以下函数执行相反的运算，将代表秒数的整数转化为`Time`对象--使用`divmod`函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "7a93edcc-de21-43b1-b0a9-1bcbc7f6125c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def int_to_time(seconds):\n",
    "    minute, second = divmod(seconds, 60)\n",
    "    hour, minute = divmod(minute, 60)\n",
    "    return make_time(hour, minute, second)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4706b5df",
   "metadata": {},
   "source": [
    "我们可以测试该函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "967fc3c2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "01:01:01\n"
     ]
    }
   ],
   "source": [
    "time = int_to_time(3661)\n",
    "print_time(time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c2b8469-d4a7-46f9-a0a1-f2a6c1595183",
   "metadata": {},
   "source": [
    "我们可以使用这些函数，编写更简洁的`add_time`版本："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "0278a042-9e5b-460f-bd26-2fa319e7193a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_time(time, hours, minutes, seconds):\n",
    "    duration = make_time(hours, minutes, seconds)\n",
    "    seconds = time_to_int(time) + time_to_int(duration)\n",
    "    return int_to_time(seconds)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb560257",
   "metadata": {},
   "source": [
    "第1行将参数转化为`Time`对象`duration`，第2行将`time`与`duration`转化为秒数并相加，第3行将求和转化为`Time`对象并返回该对象。\n",
    "\n",
    "以下是使用该函数的示例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "ee78ffbc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11:12:00\n"
     ]
    }
   ],
   "source": [
    "start = make_time(9, 40, 0)\n",
    "end = add_time(start, 1, 32, 0)\n",
    "print_time(end)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db762aa8-4aab-4c17-a88d-72c5048f18c0",
   "metadata": {},
   "source": [
    "在某些方面，从60进制转化到10进制，然后转化回去比直接处理时间困难。进制的转化更抽象，而直接处理时间值更容易。\n",
    "\n",
    "但当我们注意到时间是60进制的数字，花点时间编写函数`time_to_int`与`int_to_time`，我们将获得更短，更易于阅读调试，也更可靠的程序。\n",
    "\n",
    "以后要添加功能也更简单。例如，将两个`Time`对象相减，获得其中的持续时间。原始方案要考虑减法的借位，而使用转换函数更容易，也更可能正确。\n",
    "\n",
    "讽刺的是，有时让一个问题更困难或更宽泛，反而会让问题更简单，因为特殊情况更少，出错的可能性也越小。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0d23d08",
   "metadata": {
    "tags": []
   },
   "source": [
    "## 调试\n",
    "\n",
    "Python提供若干内建函数，对处理对象的程序进行测试和调试有帮助。例如，如果你不确定对象的类型，你可以用`type`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "652bee8f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.Time"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ec0eabf",
   "metadata": {},
   "source": [
    "你也可以使用`isinstance`检查一个对象是否是一个类的实例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "3ab974e4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(end, Time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f453fe9",
   "metadata": {},
   "source": [
    "如果你不确定一个对象是否拥有特定属性，你可以使用`hasattr`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "5f80e5ad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hasattr(start, 'hour')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0131d84",
   "metadata": {},
   "source": [
    "你可以用`vars`获得一个对象的所有属性及其值组成的字典。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "2a102f0f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'hour': 9, 'minute': 40, 'second': 0}"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1a443c8",
   "metadata": {},
   "source": [
    "我们在[11章](chap11.ipynb)使用的`structshape`模块对自定义的类型也有效。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "b71f46d8",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/structshape.py');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "1e6498a8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'tuple of 2 Time'"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from structshape import structshape\n",
    "\n",
    "t = start, end\n",
    "structshape(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "501436c0-6634-415f-be84-2d130232b2b8",
   "metadata": {},
   "source": [
    "## 术语表\n",
    "\n",
    "- **面对对象编程object-oriented programming**：使用对象组织代码和数据的风格；\n",
    "- **类class**：用户自定义的类型。类定义创建一个新的类对象；\n",
    "- **类对象 class object**：代表类的对象，是类定义的结果；\n",
    "- **实例化instantiation**：创建一个属于某类的对象的过程；\n",
    "- **实例instance**：属于一个类的一个对象；\n",
    "- **属性attribute**：与对象关联的变量，也叫做实例变量；\n",
    "- **对象图 object diagram**：对象的图形化表示，包括它的属性和值；\n",
    "- **格式标识 format specifier**：明确一个值按何种方式转化为字符串的标识；\n",
    "- **纯函数pure function**：不修改参数，除了返回值以外没有其他影响的函数；\n",
    "- **函数式编程风格 functional programming style**：尽量使用纯函数的编程风格；\n",
    "- **原型与补丁 prototype and patch**：一种程序开发方案，从粗糙的草稿开始，慢慢添加功能，修复错误；\n",
    "- **设计优先开发 design-first development**：一种程序开发方案，在写程序之前做更多规划。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09dd41c1",
   "metadata": {},
   "source": [
    "## 练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "ab3d0104",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Exception reporting mode: Verbose\n"
     ]
    }
   ],
   "source": [
    "# 这个单元格让Jupyter在出现运行时故障时提供更多调试信息。\n",
    "# 在进行练习前先运行本单元格。\n",
    "\n",
    "%xmode Verbose"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da0aea86",
   "metadata": {},
   "source": [
    "### 询问虚拟助手\n",
    "\n",
    "本章有许多新词汇。虚拟助手可以帮助你巩固只是。询问虚拟助手：\n",
    "\n",
    "- 类和类型之间的区别是什么？\n",
    "- 对象和实例之间的区别是什么？\n",
    "- 变量和属性之间的区别是什么？\n",
    "- 纯函数与非纯函数对比，各自的优缺点是什么？\n",
    "\n",
    "由于我们刚开始学习面对对象编程，所以本章中的代码并不惯用。如果你询问虚拟助手进行练习，你可能会看到尚未介绍的功能。你很可能看到叫做`__init__`的方法，它会初始化对象的属性。\n",
    "\n",
    "如果你能理解这些功能可以使用它们。否则，有点耐心，我们很快会介绍。同时，看看你能否仅用目前介绍的功能来完成以下练习。\n",
    "\n",
    "本章我们还看到一个格式标识符，你可以询问Python的f字符串中可以使用哪些标识符。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcdab7d6",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "编写函数`subtract_time`，接受两个时间对象，返回中间的持续时间，以秒为单位。假设两个时间都是同一天中的时间。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5033ee5f",
   "metadata": {
    "tags": []
   },
   "source": [
    "以下是该函数的框架："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d898f43",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def subtract_time(t1, t2):\n",
    "    \"\"\"计算两个时刻之间的秒数。\n",
    "    \n",
    "    >>> subtract_time(make_time(3, 2, 1), make_time(3, 2, 0))\n",
    "    1\n",
    "    >>> subtract_time(make_time(3, 2, 1), make_time(3, 0, 0))\n",
    "    121\n",
    "    >>> subtract_time(make_time(11, 12, 0), make_time(9, 40, 0))\n",
    "    5520\n",
    "    \"\"\"\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f1b54959",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73334265",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以使用`doctest`测试你的函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a25a3de",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from doctest import run_docstring_examples\n",
    "\n",
    "def run_doctests(func):\n",
    "    run_docstring_examples(func, globals(), name=func.__name__)\n",
    "\n",
    "run_doctests(subtract_time)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3189549",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "编写函数`is_after`，接受两个时间对象，如果第1个时间比第2个晚返回`True`,否则返回`False`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a258207b",
   "metadata": {},
   "source": [
    "以下是该函数的框架："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05499ffa",
   "metadata": {},
   "outputs": [],
   "source": [
    "def is_after(t1, t2):\n",
    "    \"\"\"检查`t1`是否晚于`t2`.\n",
    "    \n",
    "    >>> is_after(make_time(3, 2, 1), make_time(3, 2, 0))\n",
    "    True\n",
    "    >>> is_after(make_time(3, 2, 1), make_time(3, 2, 1))\n",
    "    False\n",
    "    >>> is_after(make_time(11, 12, 0), make_time(9, 40, 0))\n",
    "    True\n",
    "    \"\"\"\n",
    "    return True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12b4ad17",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9da8ede",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以使用`doctest`测试你的函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "4e580404",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "run_doctests(is_after)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16dff862",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "以下`Date`类的定义，表示某年某月某日。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5de60ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Date:\n",
    "    \"\"\"代表年月日的日期类。\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3311fa97",
   "metadata": {},
   "source": [
    "1. 编写函数`make_date`，接受`year`, `month`, `day` ，生成一个`Date`对象，将参数的值赋值给对象的属性，返回新的对象。创建一个代表1933年6月22日的对象。\n",
    "2. 编写函数`print_date`，介绍`Date`对象，使用f字符串格式化属性，打印结果。如果你用刚才创建的日期测试，结果应该为`1933-06-22`。\n",
    "3. 编写函数`is_after`，接受两个`Date`对象，如果第1个在第2个之后返回`True`。创建代表1933年9月17日的对象，检验它是否比1933年6月22日晚。\n",
    "\n",
    "提示：你可以编写函数`date_to_tuple`，接受日期对象，返回包含年/月/日的元组，这对比较很有用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcdcbc1e",
   "metadata": {},
   "source": [
    "以下是创建日期对象的函数框架："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "9e16bcde",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def make_date(year, month, day):\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff95300b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20c5edf8",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以用以下例子测试`make_date`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "62180007",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "birthday1 = make_date(1933, 6, 22)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "2d70104e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "birthday2 = make_date(1933, 9, 17)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16ff5bef",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以用以下函数作为打印日期函数的框架："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "2cc0653e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def print_date(date):\n",
    "    print('')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b8f63df",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c30d8da",
   "metadata": {},
   "source": [
    "你可以用以下例子测试`print_date`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "2d0a026d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "print_date(birthday1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17565b1e",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以用以下函数作为比较日期早晚的函数框架："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "70413f48",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def is_after(date1, date2):\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b244a057",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5fab04bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这作答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1cf29c7",
   "metadata": {},
   "source": [
    "你可以用以下例子测试`is_after`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59166d30",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "is_after(birthday1, birthday2)  #  False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c33706ee",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "is_after(birthday2, birthday1)  #  True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7f4edf8",
   "metadata": {
    "tags": []
   },
   "source": [
    "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n",
    "\n",
    "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n",
    "\n",
    "Code license: [MIT License](https://mit-license.org/)\n",
    "\n",
    "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
